fix(rules): improve precision of 4 high-FP dotnet opengrep rules#63
fix(rules): improve precision of 4 high-FP dotnet opengrep rules#63David Larsen (dc-larsen) wants to merge 4 commits intomainfrom
Conversation
Addresses customer SAST evaluation feedback where 4 rules produced 150/170 false positives (88% of all FPs), inflating the reported FP rate to 91%. Rules fixed: - dotnet-xss-response-write: Convert to taint mode. Previously matched any .Write() call including Serilog ITextFormatter log sinks. Now requires data flow from user input sources to Response.Write sinks. - dotnet-hardcoded-credentials: Add value inspection and credential API patterns. Previously matched on variable names alone, flagging config key paths like "UseCaptchaOnResetPassword". - dotnet-crypto-failures: Target actual weak algorithms (3DES, DES, RC2, RijndaelManaged) instead of Encoding.UTF8.GetBytes() which flagged the recommended SHA256.HashData(Encoding.UTF8.GetBytes(...)) pattern. - dotnet-path-traversal: Convert to taint mode. Previously matched all Path.Combine() calls including those using framework-provided paths like _env.WebRootPath. Validated with opengrep v1.19.0 against NIST Juliet C# test suite: xss-response-write: Prec 41.6% -> 100%, Recall 47.8% -> 24.3% hardcoded-credentials: Prec 0.0% -> 100%, Recall 0.0% -> 3.6% crypto-failures: Prec 36.7% -> 100%, Recall 51.4% -> 50.0% path-traversal: Prec 0.0% -> 100%, Recall 0.0% -> 45.2%
4958b6f to
cdb7224
Compare
E2E Validation: dotnet rule precision (round 1)Validated the updated rules against two intentionally vulnerable .NET repositories using opengrep v1.19.0 and the full socket-basics pipeline. Test Targets
Resultsthe-most-vulnerable-dotnet-app
AspGoat
Pipeline Integration
ObservationThe taint-mode rules correctly eliminate pattern-match false positives. Initial taint sources target classic ASP.NET WebForms ( |
E2E Validation: ASP.NET Core coverage added (round 2)Extended taint sources and sinks to cover ASP.NET Core patterns, then re-validated against both test repos. ChangesSources (both
Sinks (
Sinks (
Resultsthe-most-vulnerable-dotnet-app
AspGoat
Validation
|
…net rules Add controller parameter binding sources ([FromQuery], [FromBody], [FromRoute], [FromForm]) and IFormFile.FileName to path-traversal and XSS taint rules. Add Response.WriteAsync and Html.Raw as XSS sinks. Add fully-qualified System.IO.File.* sink variants for ASP.NET Core code that uses explicit namespace qualification. E2E tested against two vulnerable .NET repos: 7 true positives found, zero false positives.
There was a problem hiding this comment.
David Larsen (@dc-larsen) Thanks for putting this together! Reducing 150 false positives from 4 rules is meaningful, and the move toward taint-style matching is directionally the right fix for the worst offenders here. I also validated the updated dotnet.yml on my end with opengrep --validate, and the config is syntactically valid.
I do see a few semantic issues worth tightening before merge - summarized here, but see my review comments for more details:
dotnet-path-traversal:Path.GetFullPath(...)is currently treated as a sanitizer atsocket_basics/rules/dotnet.yml:410dotnet-hardcoded-credentials: the variable-name regex got much narrower atsocket_basics/rules/dotnet.yml:161dotnet-crypto-failures: one broad pattern still looks FP-prone atsocket_basics/rules/dotnet.yml:805
Overall, I think the main idea in this PR is solid: move away from simple pattern matching where it was clearly overfiring, and add more context-aware logic. I’d just recommend tightening the three cases above so we don’t trade one class of false positives for a quieter set of false negatives or misclassifications.
- Remove Path.GetFullPath() as path-traversal sanitizer (normalizes but does not prevent traversal on its own) - Broaden hardcoded-credentials variable regex to cover idiomatic C# naming: apiKey, connectionString, privateKey, accessKey, authToken - Remove overly broad Base64 encoding pattern from crypto-failures (benign encoding/transport use generates noise)
|
David Larsen (@dc-larsen) Just one remaining tweak and we should be good to go 👍 |
The $X.StartsWith($BASE) sanitizer was matching the boolean expression
instead of marking the checked path variable as sanitized, so correctly
validated paths were still flagged as tainted.
Use focus-metavariable + by-side-effect so the sanitizer applies to $X
itself. Verified with a synthetic test case: scans of an unsanitized
File.ReadAllText still fire, but the same call guarded by
full.StartsWith("/var/data/") no longer does. Juliet CWE-23/36 results
unchanged at 432 findings (Juliet test cases do not exercise StartsWith
validation). opengrep --validate and pytest pass.
Summary
Fixes 4 dotnet opengrep rules that produced 150 of 170 total false positives (88%) in a customer SAST evaluation, inflating the reported FP rate to 91%.
.Write()including SerilogITextFormatterlog sinks (74 FPs). Now tracks data flow from user input toResponse.Write.UseCaptchaOnResetPassword(31 FPs).Encoding.UTF8.GetBytes()which triggers on the recommendedSHA256.HashData()pattern (30 FPs).Path.Combine()calls including framework paths like_env.WebRootPath(15 FPs).Benchmark data (NIST Juliet C# Test Suite)
All 4 rules achieve 100% precision (zero false positives) post-fix. Recall trade-offs are acceptable: taint-mode rules only fire when user input actually reaches the sink, which is the correct behavior for security analysis.
Customer impact
Eliminates all 150 FPs from these 4 rules. Remaining findings (36 total, 20 FP) produce a ~56% FP rate, consistent with pattern-matching SAST tools. Further tuning via community rules and per-language scoping can reduce this further.
Testing
opengrep --validatepasses on full dotnet.yml (40 rules, 0 errors)pytestpasses (139 tests, 0 failures)